Fix memory leak in legacy ping passthrough (Fixes #674, #813)

This commit is contained in:
RednedEpic 2020-07-04 16:35:48 -05:00
parent cc2bbc675f
commit 8ac5d6e13d
8 changed files with 152 additions and 53 deletions

View file

@ -60,14 +60,15 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
else future.complete(event); else future.complete(event);
})); }));
ProxyPingEvent event = future.join(); ProxyPingEvent event = future.join();
ServerPing response = event.getResponse();
GeyserPingInfo geyserPingInfo = new GeyserPingInfo( GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
event.getResponse().getDescription(), response.getDescriptionComponent().toLegacyText(),
event.getResponse().getPlayers().getOnline(), new GeyserPingInfo.Players(response.getPlayers().getMax(), response.getPlayers().getOnline()),
event.getResponse().getPlayers().getMax() new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol())
); );
if (event.getResponse().getPlayers().getSample() != null) { if (event.getResponse().getPlayers().getSample() != null) {
Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> { Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> {
geyserPingInfo.addPlayer(proxiedPlayer.getName()); geyserPingInfo.getPlayerList().add(proxiedPlayer.getName());
}); });
} }
return geyserPingInfo; return geyserPingInfo;

View file

@ -26,6 +26,7 @@
package org.geysermc.platform.spigot; package org.geysermc.platform.spigot;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -48,14 +49,15 @@ public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough {
try { try {
ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers()); ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers());
Bukkit.getPluginManager().callEvent(event); Bukkit.getPluginManager().callEvent(event);
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(),
Bukkit.getOnlinePlayers().forEach(player -> { new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers()),
geyserPingInfo.addPlayer(player.getName()); new GeyserPingInfo.Version(Bukkit.getVersion(), MinecraftConstants.PROTOCOL_VERSION) // thanks Spigot for not exposing this, just default to latest
}); );
Bukkit.getOnlinePlayers().stream().map(Player::getName).forEach(geyserPingInfo.getPlayerList()::add);
return geyserPingInfo; return geyserPingInfo;
} catch (Exception e) { } catch (Exception e) {
logger.debug("Error while getting Bukkit ping passthrough: " + e.toString()); logger.debug("Error while getting Bukkit ping passthrough: " + e.toString());
return new GeyserPingInfo(null, 0, 0); return new GeyserPingInfo(null, null, null);
} }
} }

View file

@ -26,6 +26,7 @@
package org.geysermc.platform.sponge; package org.geysermc.platform.sponge;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.MinecraftVersion;
@ -35,6 +36,7 @@ import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.EventContext; import org.spongepowered.api.event.cause.EventContext;
import org.spongepowered.api.event.server.ClientPingServerEvent; import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.StatusClient; import org.spongepowered.api.network.status.StatusClient;
import org.spongepowered.api.profile.GameProfile;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.Inet4Address; import java.net.Inet4Address;
@ -68,11 +70,18 @@ public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough {
Sponge.getEventManager().post(event); Sponge.getEventManager().post(event);
GeyserPingInfo geyserPingInfo = new GeyserPingInfo( GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
event.getResponse().getDescription().toPlain(), event.getResponse().getDescription().toPlain(),
event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline(), new GeyserPingInfo.Players(
event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax()); event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax(),
event.getResponse().getPlayers().get().getProfiles().forEach(player -> { event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline()
geyserPingInfo.addPlayer(player.getName().orElseThrow(IllegalStateException::new)); ),
}); new GeyserPingInfo.Version(
event.getResponse().getVersion().getName(),
MinecraftConstants.PROTOCOL_VERSION) // thanks for also not exposing this sponge
);
event.getResponse().getPlayers().get().getProfiles().stream()
.map(GameProfile::getName)
.map(op -> op.orElseThrow(IllegalStateException::new))
.forEach(geyserPingInfo.getPlayerList()::add);
return geyserPingInfo; return geyserPingInfo;
} }

View file

@ -59,13 +59,17 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
GeyserPingInfo geyserPingInfo = new GeyserPingInfo( GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
LegacyComponentSerializer.INSTANCE.serialize(event.getPing().getDescription(), '§'), LegacyComponentSerializer.legacy().serialize(event.getPing().getDescription(), '§'),
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline(), new GeyserPingInfo.Players(
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax() event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax(),
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline()
),
new GeyserPingInfo.Version(
event.getPing().getVersion().getName(),
event.getPing().getVersion().getProtocol()
)
); );
event.getPing().getPlayers().get().getSample().forEach(player -> { event.getPing().getPlayers().get().getSample().stream().map(ServerPing.SamplePlayer::getName).forEach(geyserPingInfo.getPlayerList()::add);
geyserPingInfo.addPlayer(player.getName());
});
return geyserPingInfo; return geyserPingInfo;
} }

View file

@ -26,8 +26,11 @@
package org.geysermc.connector.common.ping; package org.geysermc.connector.common.ping;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data; import lombok.Data;
import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -35,14 +38,56 @@ import java.util.Collection;
@Data @Data
public class GeyserPingInfo { public class GeyserPingInfo {
public final String motd; private String description;
public final int currentPlayerCount;
public final int maxPlayerCount;
@Getter private Players players;
private Collection<String> players = new ArrayList<>(); private Version version;
public void addPlayer(String username) { @JsonIgnore
players.add(username); private Collection<String> playerList = new ArrayList<>();
public GeyserPingInfo() {
}
public GeyserPingInfo(String description, Players players, Version version) {
this.description = description;
this.players = players;
this.version = version;
}
@JsonSetter("description")
void setDescription(JsonNode description) {
this.description = description.toString();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Players {
private int max;
private int online;
public Players() {
}
public Players(int max, int online) {
this.max = max;
this.online = online;
}
}
@Data
public static class Version {
private String name;
private int protocol;
public Version() {
}
public Version(String name, int protocol) {
this.name = name;
this.protocol = protocol;
}
} }
} }

View file

@ -74,8 +74,8 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setVersion(null); // Server tries to connect either way and it looks better pong.setVersion(null); // Server tries to connect either way and it looks better
pong.setIpv4Port(config.getBedrock().getPort()); pong.setIpv4Port(config.getBedrock().getPort());
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.motd != null) { if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.motd)).split("\n"); String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n");
String mainMotd = motd[0]; // First line of the motd. String mainMotd = motd[0]; // First line of the motd.
String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
@ -87,8 +87,8 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
} }
if (config.isPassthroughPlayerCounts() && pingInfo != null) { if (config.isPassthroughPlayerCounts() && pingInfo != null) {
pong.setPlayerCount(pingInfo.currentPlayerCount); pong.setPlayerCount(pingInfo.getPlayers().getOnline());
pong.setMaximumPlayerCount(pingInfo.maxPlayerCount); pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax());
} else { } else {
pong.setPlayerCount(connector.getPlayers().size()); pong.setPlayerCount(connector.getPlayers().size());
pong.setMaximumPlayerCount(config.getMaxPlayers()); pong.setMaximumPlayerCount(config.getMaxPlayers());

View file

@ -148,7 +148,7 @@ public class QueryPacketHandler {
} }
if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { if (connector.getConfig().isPassthroughMotd() && pingInfo != null) {
String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.motd)).split("\n"); String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n");
motd = javaMotd[0].trim(); // First line of the motd. motd = javaMotd[0].trim(); // First line of the motd.
} else { } else {
motd = connector.getConfig().getBedrock().getMotd1(); motd = connector.getConfig().getBedrock().getMotd1();
@ -156,8 +156,8 @@ public class QueryPacketHandler {
// If passthrough player counts is enabled lets get players from the server // If passthrough player counts is enabled lets get players from the server
if (connector.getConfig().isPassthroughPlayerCounts() && pingInfo != null) { if (connector.getConfig().isPassthroughPlayerCounts() && pingInfo != null) {
currentPlayerCount = String.valueOf(pingInfo.currentPlayerCount); currentPlayerCount = String.valueOf(pingInfo.getPlayers().getOnline());
maxPlayerCount = String.valueOf(pingInfo.maxPlayerCount); maxPlayerCount = String.valueOf(pingInfo.getPlayers().getMax());
} else { } else {
currentPlayerCount = String.valueOf(connector.getPlayers().size()); currentPlayerCount = String.valueOf(connector.getPlayers().size());
maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers()); maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers());
@ -220,7 +220,7 @@ public class QueryPacketHandler {
// Fill player names // Fill player names
if(pingInfo != null) { if(pingInfo != null) {
for (String username : pingInfo.getPlayers()) { for (String username : pingInfo.getPlayerList()) {
query.write(username.getBytes()); query.write(username.getBytes());
query.write((byte) 0x00); query.write((byte) 0x00);
} }

View file

@ -26,16 +26,21 @@
package org.geysermc.connector.ping; package org.geysermc.connector.ping;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.nukkitx.nbt.util.VarInts;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.message.TextMessage;
import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoHandler;
import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.tcp.TcpSessionFactory;
import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable { public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable {
@ -44,13 +49,10 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
public GeyserLegacyPingPassthrough(GeyserConnector connector) { public GeyserLegacyPingPassthrough(GeyserConnector connector) {
this.connector = connector; this.connector = connector;
this.pingInfo = new GeyserPingInfo(null, 0, 0);
} }
private GeyserPingInfo pingInfo; private GeyserPingInfo pingInfo;
private Client client;
/** /**
* Start legacy ping passthrough thread * Start legacy ping passthrough thread
* @param connector GeyserConnector * @param connector GeyserConnector
@ -76,15 +78,51 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
@Override @Override
public void run() { public void run() {
try { try {
this.client = new Client(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort(), new MinecraftProtocol(SubProtocol.STATUS), new TcpSessionFactory()); Socket socket = new Socket();
this.client.getSession().setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> { socket.connect(new InetSocketAddress(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort()), 5000);
this.pingInfo = new GeyserPingInfo(((TextMessage) info.getDescription()).getText(), info.getPlayerInfo().getOnlinePlayers(), info.getPlayerInfo().getMaxPlayers());
this.client.getSession().disconnect(null);
});
client.getSession().connect(); ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
} catch (Exception ex) { DataOutputStream handshake = new DataOutputStream(byteArrayStream);
ex.printStackTrace(); handshake.write(0x0);
VarInts.writeUnsignedInt(handshake, MinecraftConstants.PROTOCOL_VERSION);
VarInts.writeUnsignedInt(handshake, socket.getInetAddress().getHostAddress().length());
handshake.writeBytes(socket.getInetAddress().getHostAddress());
handshake.writeShort(socket.getPort());
VarInts.writeUnsignedInt(handshake, 1);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
VarInts.writeUnsignedInt(dataOutputStream, byteArrayStream.size());
dataOutputStream.write(byteArrayStream.toByteArray());
dataOutputStream.writeByte(0x01);
dataOutputStream.writeByte(0x00);
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
VarInts.readUnsignedInt(dataInputStream);
VarInts.readUnsignedInt(dataInputStream);
int length = VarInts.readUnsignedInt(dataInputStream);
byte[] buffer = new byte[length];
dataInputStream.readFully(buffer);
dataOutputStream.writeByte(0x09);
dataOutputStream.writeByte(0x01);
dataOutputStream.writeLong(System.currentTimeMillis());
VarInts.readUnsignedInt(dataInputStream);
String json = new String(buffer);
this.pingInfo = GeyserConnector.JSON_MAPPER.readValue(json, GeyserPingInfo.class);
byteArrayStream.close();
handshake.close();
dataOutputStream.close();
dataInputStream.close();
socket.close();
} catch (SocketTimeoutException | ConnectException ex) {
this.pingInfo = null;
this.connector.getLogger().debug("Connection timeout for ping passthrough.");
} catch (JsonParseException | JsonMappingException ex) {
this.connector.getLogger().error("Failed to parse json when pinging server!", ex);
} catch (IOException e) {
e.printStackTrace();
} }
} }
} }