BungeeCord and Velocity info forwarding support

This commit is contained in:
Nanit 2020-11-27 20:11:43 +02:00
parent d4e553e4be
commit 7953244f80
15 changed files with 280 additions and 47 deletions

View File

@ -0,0 +1,9 @@
package ru.nanit.limbo;
public final class LimboConstants {
public static final String VELOCITY_INFO_CHANNEL = "velocity:player_info";
private LimboConstants(){}
}

View File

@ -35,7 +35,7 @@ public final class LimboConfig {
public void load() throws Exception { public void load() throws Exception {
Configuration conf = YamlConfiguration.builder() Configuration conf = YamlConfiguration.builder()
.source(ConfigSources.resource("/limbo.yml", this).copyTo(root)) .source(ConfigSources.resource("/settings.yml", this).copyTo(root))
.build(); .build();
conf.reload(); conf.reload();

View File

@ -1,9 +1,11 @@
package ru.nanit.limbo.connection; package ru.nanit.limbo.connection;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import ru.nanit.limbo.LimboConstants;
import ru.nanit.limbo.protocol.packets.login.*; import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.packets.play.*;
import ru.nanit.limbo.protocol.pipeline.PacketDecoder; import ru.nanit.limbo.protocol.pipeline.PacketDecoder;
@ -17,7 +19,10 @@ import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.Logger;
import ru.nanit.limbo.util.UuidUtil; import ru.nanit.limbo.util.UuidUtil;
import ru.nanit.limbo.util.VelocityUtil;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -25,31 +30,38 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
private final LimboServer server; private final LimboServer server;
private final Channel channel; private final Channel channel;
private final GameProfile profile;
private State state; private State state;
private Version clientVersion; private Version clientVersion;
private SocketAddress address;
private UUID uuid; private int velocityLoginMessageId = -1;
private String username;
public UUID getUuid() {
return uuid;
}
public String getUsername() {
return username;
}
public ClientConnection(Channel channel, LimboServer server){ public ClientConnection(Channel channel, LimboServer server){
this.server = server; this.server = server;
this.channel = channel; this.channel = channel;
this.address = channel.remoteAddress();
this.profile = new GameProfile();
}
public UUID getUuid() {
return profile.getUuid();
}
public String getUsername() {
return profile.getUsername();
}
private void setAddress(String host){
this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort());
} }
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (state.equals(State.PLAY)){ if (state.equals(State.PLAY)){
server.getConnections().removeConnection(this); server.getConnections().removeConnection(this);
Logger.info("Player %s disconnected", this.username); Logger.info("Player %s disconnected", getUsername());
} }
super.channelInactive(ctx); super.channelInactive(ctx);
} }
@ -69,9 +81,20 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
public void handlePacket(Object packet){ public void handlePacket(Object packet){
if (packet instanceof PacketHandshake){ if (packet instanceof PacketHandshake){
PacketHandshake handshake = (PacketHandshake) packet; PacketHandshake handshake = (PacketHandshake) packet;
updateState(State.getById(handshake.getNextState()));
clientVersion = handshake.getVersion(); clientVersion = handshake.getVersion();
Logger.debug("Pinged from " + handshake.getHost() + ":" + handshake.getPort()); updateState(State.getById(handshake.getNextState()));
Logger.debug("Pinged from " + address);
if (server.getConfig().getInfoForwarding().isLegacy()){
String[] split = handshake.getHost().split("\00");
if (split.length == 3 || split.length == 4){
setAddress(split[1]);
profile.setUuid(UuidUtil.fromString(split[2]));
} else {
disconnect("You've enabled player info forwarding. To join, enable it in your proxy too");
}
}
} }
if (packet instanceof PacketStatusRequest){ if (packet instanceof PacketStatusRequest){
@ -93,23 +116,67 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
return; return;
} }
this.username = ((PacketLoginStart) packet).getUsername(); if (server.getConfig().getInfoForwarding().isModern()){
this.uuid = UuidUtil.getOfflineModeUuid(this.username); velocityLoginMessageId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
PacketLoginPluginRequest request = new PacketLoginPluginRequest();
request.setMessageId(velocityLoginMessageId);
request.setChannel(LimboConstants.VELOCITY_INFO_CHANNEL);
request.setData(Unpooled.EMPTY_BUFFER);
sendPacket(request);
return;
}
if (server.getConfig().getInfoForwarding().isNone()){
profile.setUsername(((PacketLoginStart) packet).getUsername());
profile.setUuid(UuidUtil.getOfflineModeUuid(getUsername()));
}
fireLoginSuccess();
}
if (packet instanceof PacketLoginPluginResponse){
PacketLoginPluginResponse response = (PacketLoginPluginResponse) packet;
if (server.getConfig().getInfoForwarding().isModern()
&& response.getMessageId() == velocityLoginMessageId){
if (!response.isSuccessful() || response.getData() == null){
disconnect("You need to connect with Velocity");
return;
}
if (!VelocityUtil.checkIntegrity(response.getData())) {
disconnect("Can't verify forwarded player info");
return;
}
setAddress(response.getData().readString());
profile.setUuid(response.getData().readUuid());
profile.setUsername(response.getData().readString());
fireLoginSuccess();
}
}
}
private void fireLoginSuccess(){
if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1){
disconnect("You need to connect with Velocity");
return;
}
PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); PacketLoginSuccess loginSuccess = new PacketLoginSuccess();
loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(getUsername()));
loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(this.username)); loginSuccess.setUsername(getUsername());
loginSuccess.setUsername(this.username);
sendPacket(loginSuccess); sendPacket(loginSuccess);
updateState(State.PLAY);
updateState(State.PLAY);
server.getConnections().addConnection(this); server.getConnections().addConnection(this);
Logger.info("Player %s connected (%s)", this.username, channel.remoteAddress());
Logger.info("Player %s connected (%s)", getUsername(), address);
sendJoinPackets(); sendJoinPackets();
} }
}
private void sendJoinPackets(){ private void sendJoinPackets(){
PacketJoinGame joinGame = new PacketJoinGame(); PacketJoinGame joinGame = new PacketJoinGame();

View File

@ -0,0 +1,25 @@
package ru.nanit.limbo.connection;
import java.util.UUID;
public class GameProfile {
private String username;
private UUID uuid;
public String getUsername() {
return username;
}
public UUID getUuid() {
return uuid;
}
public void setUsername(String username) {
this.username = username;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
}

View File

@ -66,7 +66,7 @@ public class ByteMessage extends ByteBuf {
return readString(readVarInt()); return readString(readVarInt());
} }
private String readString(int length) { public String readString(int length) {
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
buf.skipBytes(length); buf.skipBytes(length);
return str; return str;

View File

@ -0,0 +1,32 @@
package ru.nanit.limbo.protocol.packets.login;
import io.netty.buffer.ByteBuf;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
public class PacketLoginPluginRequest implements PacketOut {
private int messageId;
private String channel;
private ByteBuf data;
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public void setChannel(String channel) {
this.channel = channel;
}
public void setData(ByteBuf data) {
this.data = data;
}
@Override
public void encode(ByteMessage msg) {
msg.writeVarInt(messageId);
msg.writeString(channel);
msg.writeBytes(data);
}
}

View File

@ -0,0 +1,35 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketIn;
public class PacketLoginPluginResponse implements PacketIn {
private int messageId;
private boolean successful;
private ByteMessage data;
public int getMessageId() {
return messageId;
}
public boolean isSuccessful() {
return successful;
}
public ByteMessage getData() {
return data;
}
@Override
public void decode(ByteMessage msg) {
messageId = msg.readVarInt();
successful = msg.readBoolean();
if (msg.readableBytes() > 0){
int i = msg.readableBytes();
data = new ByteMessage(msg.readBytes(i));
}
}
}

View File

@ -41,5 +41,4 @@ public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
public void updateState(State state){ public void updateState(State state){
this.mappings = state.serverBound; this.mappings = state.serverBound;
} }
} }

View File

@ -30,8 +30,10 @@ public enum State {
LOGIN(2){ LOGIN(2){
{ {
serverBound.register(0x00, PacketLoginStart::new); serverBound.register(0x00, PacketLoginStart::new);
serverBound.register(0x02, PacketLoginPluginResponse::new);
clientBound.register(0x00, PacketDisconnect::new); clientBound.register(0x00, PacketDisconnect::new);
clientBound.register(0x02, PacketLoginSuccess::new); clientBound.register(0x02, PacketLoginSuccess::new);
clientBound.register(0x04, PacketLoginPluginRequest::new);
} }
}, },
PLAY(3){ PLAY(3){

View File

@ -12,6 +12,7 @@ import ru.nanit.limbo.protocol.packets.play.PacketBossBar;
import ru.nanit.limbo.protocol.packets.play.PacketChatMessage; import ru.nanit.limbo.protocol.packets.play.PacketChatMessage;
import ru.nanit.limbo.server.data.*; import ru.nanit.limbo.server.data.*;
import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.Logger;
import ru.nanit.limbo.util.VelocityUtil;
import ru.nanit.limbo.world.DimensionRegistry; import ru.nanit.limbo.world.DimensionRegistry;
import java.net.SocketAddress; import java.net.SocketAddress;
@ -64,6 +65,10 @@ public final class LimboServer {
Logger.setLevel(config.getDebugLevel()); Logger.setLevel(config.getDebugLevel());
if (config.getInfoForwarding().isModern()){
VelocityUtil.init(config);
}
dimensionRegistry = new DimensionRegistry(); dimensionRegistry = new DimensionRegistry();
dimensionRegistry.load(config.getDimensionType()); dimensionRegistry.load(config.getDimensionType());

View File

@ -4,19 +4,31 @@ import napi.configurate.data.ConfigNode;
import napi.configurate.serializing.NodeSerializer; import napi.configurate.serializing.NodeSerializer;
import napi.configurate.serializing.NodeSerializingException; import napi.configurate.serializing.NodeSerializingException;
import java.util.Optional; import java.nio.charset.StandardCharsets;
public class InfoForwarding { public class InfoForwarding {
private Type type; private Type type;
private String secret; private byte[] secretKey;
public Type getType() { public Type getType() {
return type; return type;
} }
public Optional<String> getSecret() { public byte[] getSecretKey() {
return Optional.ofNullable(secret); return secretKey;
}
public boolean isNone(){
return type == Type.NONE;
}
public boolean isLegacy(){
return type == Type.LEGACY;
}
public boolean isModern(){
return type == Type.MODERN;
} }
public enum Type { public enum Type {
@ -38,14 +50,14 @@ public class InfoForwarding {
} }
if (forwarding.type == Type.MODERN){ if (forwarding.type == Type.MODERN){
forwarding.secret = node.getNode("secret").getString(); forwarding.secretKey = node.getNode("secret").getString().getBytes(StandardCharsets.UTF_8);
} }
return forwarding; return forwarding;
} }
@Override @Override
public void serialize(InfoForwarding infoForwarding, ConfigNode configNode) throws NodeSerializingException { public void serialize(InfoForwarding infoForwarding, ConfigNode configNode) {
} }
} }

View File

@ -12,4 +12,9 @@ public final class UuidUtil {
.getBytes(StandardCharsets.UTF_8)); .getBytes(StandardCharsets.UTF_8));
} }
public static UUID fromString(String str){
if(str.contains("-")) return UUID.fromString(str);
return UUID.fromString(str.replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"));
}
} }

View File

@ -0,0 +1,42 @@
package ru.nanit.limbo.util;
import io.netty.buffer.ByteBuf;
import ru.nanit.limbo.configuration.LimboConfig;
import ru.nanit.limbo.protocol.ByteMessage;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
public final class VelocityUtil {
private static byte[] secretKey;
private VelocityUtil(){}
public static void init(LimboConfig config){
secretKey = config.getInfoForwarding().getSecretKey();
}
public static boolean checkIntegrity(ByteMessage buf) {
byte[] signature = new byte[32];
buf.readBytes(signature);
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), data);
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secretKey, "HmacSHA256"));
byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature))
return false;
} catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
int version = buf.readVarInt();
if (version != 1)
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001');
return true;
}
}

View File

@ -57,12 +57,12 @@ public final class DimensionRegistry {
overWorld = CompoundBinaryTag.builder() overWorld = CompoundBinaryTag.builder()
.putString("name", "minecraft:overworld") .putString("name", "minecraft:overworld")
.putByte("piglin_safe", (byte) 0) .putByte("piglin_safe", (byte) 0)
.putByte("natural", (byte) 0) .putByte("natural", (byte) 1)
.putFloat("ambient_light", 0.0F) .putFloat("ambient_light", 0.0F)
.putString("infiniburn", "minecraft:infiniburn_overworld") .putString("infiniburn", "minecraft:infiniburn_overworld")
.putByte("respawn_anchor_works", (byte) 0) .putByte("respawn_anchor_works", (byte) 0)
.putByte("has_skylight", (byte) 0) .putByte("has_skylight", (byte) 1)
.putByte("bed_works", (byte) 0) .putByte("bed_works", (byte) 1)
.putString("effects", "minecraft:overworld") .putString("effects", "minecraft:overworld")
.putLong("fixed_time", 6000L) .putLong("fixed_time", 6000L)
.putByte("has_raids", (byte) 1) .putByte("has_raids", (byte) 1)
@ -74,20 +74,20 @@ public final class DimensionRegistry {
nether = CompoundBinaryTag.builder() nether = CompoundBinaryTag.builder()
.putString("name", "minecraft:the_nether") .putString("name", "minecraft:the_nether")
.putByte("piglin_safe", (byte) 0) .putByte("piglin_safe", (byte) 1)
.putByte("natural", (byte) 0) .putByte("natural", (byte) 0)
.putFloat("ambient_light", 0.0F) .putFloat("ambient_light", 0.1F)
.putString("infiniburn", "minecraft:infiniburn_nether") .putString("infiniburn", "minecraft:infiniburn_nether")
.putByte("respawn_anchor_works", (byte) 0) .putByte("respawn_anchor_works", (byte) 1)
.putByte("has_skylight", (byte) 0) .putByte("has_skylight", (byte) 0)
.putByte("bed_works", (byte) 0) .putByte("bed_works", (byte) 0)
.putString("effects", "minecraft:the_nether") .putString("effects", "minecraft:the_nether")
.putLong("fixed_time", 18000L) .putLong("fixed_time", 18000L)
.putByte("has_raids", (byte) 1) .putByte("has_raids", (byte) 0)
.putInt("logical_height", 128) .putInt("logical_height", 128)
.putDouble("coordinate_scale", 1.0) .putDouble("coordinate_scale", 1.0)
.putByte("ultrawarm", (byte) 0) .putByte("ultrawarm", (byte) 1)
.putByte("has_ceiling", (byte) 0) .putByte("has_ceiling", (byte) 1)
.build(); .build();
theEnd = CompoundBinaryTag.builder() theEnd = CompoundBinaryTag.builder()
@ -110,13 +110,13 @@ public final class DimensionRegistry {
CompoundBinaryTag overWorldData = CompoundBinaryTag.builder() CompoundBinaryTag overWorldData = CompoundBinaryTag.builder()
.putString("name", "minecraft:overworld") .putString("name", "minecraft:overworld")
.putInt("id", 2) .putInt("id", 0)
.put("element", overWorld) .put("element", overWorld)
.build(); .build();
CompoundBinaryTag netherData = CompoundBinaryTag.builder() CompoundBinaryTag netherData = CompoundBinaryTag.builder()
.putString("name", "minecraft:the_nether") .putString("name", "minecraft:the_nether")
.putInt("id", 2) .putInt("id", 1)
.put("element", nether) .put("element", nether)
.build(); .build();

View File

@ -21,7 +21,7 @@ dimension: THE_END
# Spawn position in the world # Spawn position in the world
spawnPosition: spawnPosition:
x: 0.0 x: 0.0
y: 0.0 y: 32.0
z: 0.0 z: 0.0
yaw: 0.0 yaw: 0.0
pitch: 0.0 pitch: 0.0