Support Bungeecord IP forwarding

This commit is contained in:
LOOHP 2020-08-04 19:07:19 +08:00
parent c2077a2a5c
commit b01cbad932
13 changed files with 401 additions and 21 deletions

View File

@ -9,6 +9,7 @@ import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import com.loohp.limbo.Player.Player;
import com.loohp.limbo.Utils.CustomStringUtils;
@ -67,10 +68,76 @@ public class Console {
public class ConsoleOutputStream extends PrintStream {
public ConsoleOutputStream(OutputStream out) {
public ConsoleOutputStream(OutputStream out) {
super(out);
}
@Override
public PrintStream printf(Locale l, String format, Object... args) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
return super.printf(l, "[" + date + "] " + format, args);
}
@Override
public PrintStream printf(String format, Object... args) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
return super.printf("[" + date + "] " + format, args);
}
@Override
public void println() {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] ");
}
@Override
public void println(boolean x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(char x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(char[] x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + String.valueOf(x));
}
@Override
public void println(double x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(float x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(int x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(long x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(Object x) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());
super.println("[" + date + "] " + x);
}
@Override
public void println(String string) {
String date = new SimpleDateFormat("HH':'mm':'ss").format(new Date());

View File

@ -33,6 +33,7 @@ public class ServerProperties {
String motdJson;
String versionString;
int protocol;
boolean bungeecord;
Optional<BufferedImage> favicon;
@ -63,6 +64,7 @@ public class ServerProperties {
allowFlight = Boolean.parseBoolean(prop.getProperty("allow-flight"));
motdJson = prop.getProperty("motd");
versionString = prop.getProperty("version");
bungeecord = Boolean.parseBoolean(prop.getProperty("bungeecord"));
File png = new File("server-icon.png");
if (png.exists()) {
@ -84,6 +86,10 @@ public class ServerProperties {
System.out.println("Loaded server.properties");
}
public boolean isBungeecord() {
return bungeecord;
}
public Optional<BufferedImage> getFavicon() {
return favicon;
}

View File

@ -215,7 +215,7 @@ public class Limbo {
base = base.replace("%PROTOCOL%", String.valueOf(properties.getProtocol()));
base = base.replace("%MOTD%", properties.getMotdJson());
base = base.replace("%MAXPLAYERS%", String.valueOf(properties.getMaxPlayers()));
base = base.replace("%ONLINECLIENTS%", String.valueOf(server.getClients().size()));
base = base.replace("%ONLINECLIENTS%", String.valueOf(getPlayers().size()));
if (properties.getFavicon().isPresent()) {
String icon = "\"favicon\":\"data:image/png;base64," + ImageUtils.imgToBase64String(properties.getFavicon().get(), "png") + "\",";

View File

@ -3,6 +3,7 @@ package com.loohp.limbo.Server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
@ -16,6 +17,7 @@ import com.loohp.limbo.Player.Player;
import com.loohp.limbo.Server.Packets.Packet;
import com.loohp.limbo.Server.Packets.PacketHandshakingIn;
import com.loohp.limbo.Server.Packets.PacketLoginInLoginStart;
import com.loohp.limbo.Server.Packets.PacketLoginOutDisconnect;
import com.loohp.limbo.Server.Packets.PacketLoginOutLoginSuccess;
import com.loohp.limbo.Server.Packets.PacketOut;
import com.loohp.limbo.Server.Packets.PacketPlayInChat;
@ -42,8 +44,8 @@ import com.loohp.limbo.Server.Packets.PacketStatusOutPong;
import com.loohp.limbo.Server.Packets.PacketStatusOutResponse;
import com.loohp.limbo.Utils.CustomStringUtils;
import com.loohp.limbo.Utils.DataTypeIO;
import com.loohp.limbo.Utils.SkinUtils;
import com.loohp.limbo.Utils.SkinUtils.SkinResponse;
import com.loohp.limbo.Utils.MojangAPIUtils;
import com.loohp.limbo.Utils.MojangAPIUtils.SkinResponse;
import com.loohp.limbo.World.BlockPosition;
import com.loohp.limbo.World.DimensionRegistry;
import com.loohp.limbo.World.World;
@ -73,10 +75,17 @@ public class ClientConnection extends Thread {
private DataOutputStream output;
private InetAddress iNetAddress;
public ClientConnection(Socket client_socket) {
this.client_socket = client_socket;
this.iNetAddress = client_socket.getInetAddress();
}
public InetAddress getiNetAddress() {
return iNetAddress;
}
public long getLastKeepAlivePayLoad() {
return lastKeepAlivePayLoad;
}
@ -124,6 +133,23 @@ public class ClientConnection extends Thread {
}
}
public void disconnectDuringLogin(BaseComponent[] reason) {
try {
PacketLoginOutDisconnect packet = new PacketLoginOutDisconnect(ComponentSerializer.toString(reason));
byte[] packetByte = packet.serializePacket();
DataTypeIO.writeVarInt(output, packetByte.length);
output.write(packetByte);
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
client_socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
running = true;
@ -133,8 +159,17 @@ public class ClientConnection extends Thread {
DataInputStream input = new DataInputStream(client_socket.getInputStream());
output = new DataOutputStream(client_socket.getOutputStream());
DataTypeIO.readVarInt(input);
int handShakeId = DataTypeIO.readVarInt(input);
PacketHandshakingIn handshake = (PacketHandshakingIn) Packet.getHandshakeIn().get(handShakeId).getConstructor(DataInputStream.class).newInstance(input);
//int handShakeId = DataTypeIO.readVarInt(input);
DataTypeIO.readVarInt(input);
PacketHandshakingIn handshake = new PacketHandshakingIn(input);
boolean isBungeecord = Limbo.getInstance().getServerProperties().isBungeecord();
String bungeeForwarding = handshake.getServerAddress();
UUID bungeeUUID = null;
SkinResponse bungeeSkin = null;
switch (handshake.getHandshakeType()) {
case STATUS:
state = ClientState.STATUS;
@ -146,7 +181,7 @@ public class ClientConnection extends Thread {
//do nothing
} else if (packetType.equals(PacketStatusInRequest.class)) {
String str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort();
System.out.println("[/" + str + "] <-> InitialHandler has pinged");
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Handshake Status has pinged");
PacketStatusOutResponse packet = new PacketStatusOutResponse(Limbo.getInstance().getServerListResponseJson());
sendPacket(packet);
} else if (packetType.equals(PacketStatusInPing.class)) {
@ -159,6 +194,27 @@ public class ClientConnection extends Thread {
break;
case LOGIN:
state = ClientState.LOGIN;
if (isBungeecord) {
try {
String[] data = bungeeForwarding.split("\\x00");
//String host = data[0];
String ip = data[1];
bungeeUUID = UUID.fromString(data[2].replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5"));
iNetAddress = InetAddress.getByName(ip);
String skinJson = data[3];
String skin = skinJson.split("\"value\":\"")[1].split("\"")[0];
String signature = skinJson.split("\"signature\":\"")[1].split("\"")[0];
bungeeSkin = new SkinResponse(skin, signature);
} catch (Exception e) {
Limbo.getInstance().getConsole().sendMessage("If you wish to use bungeecord's IP forwarding, please enable that in your bungeecord config.yml as well!");
disconnectDuringLogin(new BaseComponent[] {new TextComponent(ChatColor.RED + "Please connect from the proxy!")});
}
}
while (client_socket.isConnected()) {
int size = DataTypeIO.readVarInt(input);
int packetId = DataTypeIO.readVarInt(input);
@ -169,7 +225,8 @@ public class ClientConnection extends Thread {
} else if (packetType.equals(PacketLoginInLoginStart.class)) {
PacketLoginInLoginStart start = new PacketLoginInLoginStart(input);
String username = start.getUsername();
UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
UUID uuid = isBungeecord ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(uuid, username);
sendPacket(success);
@ -216,7 +273,7 @@ public class ClientConnection extends Thread {
player.setLocation(new Location(world, s.getX(), s.getY(), s.getZ(), s.getYaw(), s.getPitch()));
sendPacket(positionLook);
SkinResponse skinresponce = SkinUtils.getSkinFromMojangServer(player.getName());
SkinResponse skinresponce = isBungeecord ? bungeeSkin : MojangAPIUtils.getSkinFromMojangServer(player.getName());
PlayerSkinProperty skin = skinresponce != null ? new PlayerSkinProperty(skinresponce.getSkin(), skinresponce.getSignature()) : null;
PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo(PlayerInfoAction.ADD_PLAYER, player.getUUID(), new PlayerInfoData.PlayerInfoDataAddPlayer(player.getName(), Optional.ofNullable(skin), p.getDefaultGamemode(), 0, false, Optional.empty()));
sendPacket(info);
@ -240,7 +297,7 @@ public class ClientConnection extends Thread {
sendPacket(abilities);
String str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort() + "|" + player.getName();
System.out.println("[/" + str + "] <-> Player had connected to the Limbo server!");
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!");
while (client_socket.isConnected()) {
try {
@ -272,7 +329,7 @@ public class ClientConnection extends Thread {
} else if (packetType.equals(PacketPlayInKeepAlive.class)) {
PacketPlayInKeepAlive alive = new PacketPlayInKeepAlive(input);
if (alive.getPayload() != lastKeepAlivePayLoad) {
System.out.println("Incorrect Payload recieved in KeepAlive packet for player " + player.getName());
Limbo.getInstance().getConsole().sendMessage("Incorrect Payload recieved in KeepAlive packet for player " + player.getName());
break;
}
} else if (packetType.equals(PacketPlayInChat.class)) {
@ -299,7 +356,7 @@ public class ClientConnection extends Thread {
}
}
str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort() + "|" + player.getName();
System.out.println("[/" + str + "] <-> Player had disconnected!");
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had disconnected!");
}
} catch (Exception e) {}

View File

@ -0,0 +1,43 @@
package com.loohp.limbo.Server.Packets;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.loohp.limbo.Utils.DataTypeIO;
import com.loohp.limbo.Utils.NamespacedKey;
public class PacketLoginInPluginMessaging extends PacketIn {
private int messageId;
private NamespacedKey channel;
private byte[] data;
public PacketLoginInPluginMessaging(int messageId, NamespacedKey channel, byte[] data) {
this.messageId = messageId;
this.channel = channel;
this.data = data;
}
public PacketLoginInPluginMessaging(DataInputStream in, int packetLength, int packetId) throws IOException {
messageId = DataTypeIO.readVarInt(in);
String rawChannel = DataTypeIO.readString(in);
channel = new NamespacedKey(rawChannel);
int dataLength = packetLength - DataTypeIO.getVarIntLength(packetId) - DataTypeIO.getVarIntLength(messageId) - DataTypeIO.getStringLength(rawChannel, StandardCharsets.UTF_8);
data = new byte[dataLength];
in.read(data);
}
public int getMessageId() {
return messageId;
}
public NamespacedKey getChannel() {
return channel;
}
public byte[] getData() {
return data;
}
}

View File

@ -0,0 +1,48 @@
package com.loohp.limbo.Server.Packets;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.loohp.limbo.Utils.DataTypeIO;
import com.loohp.limbo.Utils.NamespacedKey;
public class PacketLoginOutPluginMessaging extends PacketOut {
private int messageId;
private NamespacedKey channel;
private byte[] data;
public PacketLoginOutPluginMessaging(int messageId, NamespacedKey channel, byte[] data) {
this.messageId = messageId;
this.channel = channel;
this.data = data;
}
public int getMessageId() {
return messageId;
}
public NamespacedKey getChannel() {
return channel;
}
public byte[] getData() {
return data;
}
@Override
public byte[] serializePacket() throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(buffer);
output.writeByte(Packet.getLoginOut().get(getClass()));
DataTypeIO.writeVarInt(output, messageId);
DataTypeIO.writeString(output, channel.toString(), StandardCharsets.UTF_8);
output.write(data);
return buffer.toByteArray();
}
}

View File

@ -0,0 +1,36 @@
package com.loohp.limbo.Server.Packets;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.loohp.limbo.Utils.DataTypeIO;
import com.loohp.limbo.Utils.NamespacedKey;
public class PacketPlayInPluginMessaging extends PacketIn {
private NamespacedKey channel;
private byte[] data;
public PacketPlayInPluginMessaging(NamespacedKey channel, byte[] data) {
this.channel = channel;
this.data = data;
}
public PacketPlayInPluginMessaging(DataInputStream in, int packetLength, int packetId) throws IOException {
String rawChannel = DataTypeIO.readString(in);
channel = new NamespacedKey(rawChannel);
int dataLength = packetLength - DataTypeIO.getVarIntLength(packetId) - DataTypeIO.getStringLength(rawChannel, StandardCharsets.UTF_8);
data = new byte[dataLength];
in.read(data);
}
public NamespacedKey getChannel() {
return channel;
}
public byte[] getData() {
return data;
}
}

View File

@ -0,0 +1,41 @@
package com.loohp.limbo.Server.Packets;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.loohp.limbo.Utils.DataTypeIO;
import com.loohp.limbo.Utils.NamespacedKey;
public class PacketPlayOutPluginMessaging extends PacketOut {
private NamespacedKey channel;
private byte[] data;
public PacketPlayOutPluginMessaging(NamespacedKey channel, byte[] data) {
this.channel = channel;
this.data = data;
}
public NamespacedKey getChannel() {
return channel;
}
public byte[] getData() {
return data;
}
@Override
public byte[] serializePacket() throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(buffer);
output.writeByte(Packet.getPlayOut().get(getClass()));
DataTypeIO.writeString(output, channel.toString(), StandardCharsets.UTF_8);
output.write(data);
return buffer.toByteArray();
}
}

View File

@ -0,0 +1,58 @@
package com.loohp.limbo.Utils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.loohp.limbo.Server.Packets.PacketPlayOutPluginMessaging;
public class BungeeLoginMessageUtils {
public static void sendUUIDRequest(DataOutputStream output) throws IOException {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("UUID");
PacketPlayOutPluginMessaging packet = new PacketPlayOutPluginMessaging(new NamespacedKey("bungeecord", "main"), out.toByteArray());
byte[] packetByte = packet.serializePacket();
DataTypeIO.writeVarInt(output, packetByte.length);
output.write(packetByte);
}
public static UUID readUUIDResponse(byte[] data) {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
String subchannel = in.readUTF();
if (subchannel.equals("UUID")) {
return UUID.fromString(in.readUTF());
} else {
throw new RuntimeException("Bungeecord Message receieved is not an IP");
}
}
public static void sendIPRequest(DataOutputStream output) throws IOException {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("IP");
PacketPlayOutPluginMessaging packet = new PacketPlayOutPluginMessaging(new NamespacedKey("bungeecord", "main"), out.toByteArray());
byte[] packetByte = packet.serializePacket();
DataTypeIO.writeVarInt(output, packetByte.length);
output.write(packetByte);
}
public static InetAddress readIPResponse(byte[] data) throws UnknownHostException {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
String subchannel = in.readUTF();
if (subchannel.equals("IP")) {
String ip = in.readUTF();
in.readInt();
return InetAddress.getByName(ip);
} else {
throw new RuntimeException("Bungeecord Message receieved is not an IP");
}
}
}

View File

@ -40,6 +40,11 @@ public class DataTypeIO {
return new String(b);
}
public static int getStringLength(String string, Charset charset) throws IOException {
byte[] bytes = string.getBytes(charset);
return bytes.length;
}
public static void writeString(DataOutputStream out, String string, Charset charset) throws IOException {
byte[] bytes = string.getBytes(charset);
writeVarInt(out, bytes.length);

View File

@ -9,7 +9,7 @@ import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
public class SkinUtils {
public class MojangAPIUtils {
public static class SkinResponse {
@ -31,7 +31,7 @@ public class SkinUtils {
}
public static SkinResponse getSkinFromMojangServer(String username) {
public static UUID getOnlineUUIDOfPlayerFromMojang(String username) {
try {
URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + username);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
@ -42,8 +42,12 @@ public class SkinUtils {
connection.addRequestProperty("Pragma", "no-cache");
if (connection.getResponseCode() == HttpsURLConnection.HTTP_OK) {
String reply = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
String uuid = reply.split("\"id\":\"")[1].split("\"")[0];
return getSkinFromMojangServer(UUID.fromString(uuid.replaceFirst( "([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5" )));
if (!reply.contains("\"error\":\"BadRequestException\"")) {
String uuid = reply.split("\"id\":\"")[1].split("\"")[0];
return UUID.fromString(uuid.replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5"));
} else {
return null;
}
} else {
System.out.println("Connection could not be opened (Response code " + connection.getResponseCode() + ", " + connection.getResponseMessage() + ")");
return null;
@ -54,6 +58,14 @@ public class SkinUtils {
}
}
public static SkinResponse getSkinFromMojangServer(String username) {
UUID uuid = getOnlineUUIDOfPlayerFromMojang(username);
if (uuid == null) {
return null;
}
return getSkinFromMojangServer(uuid);
}
public static SkinResponse getSkinFromMojangServer(UUID uuid) {
try {
URL url = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid.toString() + "?unsigned=false");

View File

@ -3,18 +3,21 @@
"0x00": "PacketHandshakingIn"
},
"LoginIn": {
"0x00": "PacketLoginInLoginStart"
"0x00": "PacketLoginInLoginStart",
"0x02": "PacketLoginInPluginMessaging"
},
"LoginOut": {
"PacketLoginOutLoginSuccess": "0x02",
"PacketLoginOutDisconnect": "0x00"
"PacketLoginOutDisconnect": "0x00",
"PacketLoginOutPluginMessaging": "0x04",
},
"PlayIn": {
"0x10": "PacketPlayInKeepAlive",
"0x03": "PacketPlayInChat",
"0x13": "PacketPlayInPositionAndLook",
"0x12": "PacketPlayInPosition",
"0x14": "PacketPlayInRotation"
"0x14": "PacketPlayInRotation",
"0x0B": "PacketPlayInPluginMessaging"
},
"PlayOut": {
"PacketPlayOutLogin": "0x25",
@ -27,7 +30,8 @@
"PacketPlayOutPlayerInfo": "0x33",
"PacketPlayOutUpdateViewPosition": "0x40",
"PacketPlayOutShowPlayerSkins": "0x44",
"PacketPlayOutDisconnect": "0x1A"
"PacketPlayOutDisconnect": "0x1A",
"PacketPlayOutPluginMessaging": "0x17"
},
"StatusIn": {
"0x01": "PacketStatusInPing",

View File

@ -7,6 +7,9 @@ server-port=30000
#Server ip, localhost for local access only
server-ip=localhost
#Whether this is server is behind a bungeecord proxy
bungeecord=true
#World Name and the Schematic file containing map
level-name=world;spawn.schem