Skip to content

Commit

Permalink
Fix memory leak in legacy ping passthrough (Fixes #674, #813)
Browse files Browse the repository at this point in the history
  • Loading branch information
Redned235 committed Jul 4, 2020
1 parent cc2bbc6 commit 8ac5d6e
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ public GeyserPingInfo getPingInformation() {
else future.complete(event);
}));
ProxyPingEvent event = future.join();
ServerPing response = event.getResponse();
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
event.getResponse().getDescription(),
event.getResponse().getPlayers().getOnline(),
event.getResponse().getPlayers().getMax()
response.getDescriptionComponent().toLegacyText(),
new GeyserPingInfo.Players(response.getPlayers().getMax(), response.getPlayers().getOnline()),
new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol())
);
if (event.getResponse().getPlayers().getSample() != null) {
Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> {
geyserPingInfo.addPlayer(proxiedPlayer.getName());
geyserPingInfo.getPlayerList().add(proxiedPlayer.getName());
});
}
return geyserPingInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

package org.geysermc.platform.spigot;

import com.github.steveice10.mc.protocol.MinecraftConstants;
import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
Expand All @@ -48,14 +49,15 @@ public GeyserPingInfo getPingInformation() {
try {
ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers());
Bukkit.getPluginManager().callEvent(event);
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
Bukkit.getOnlinePlayers().forEach(player -> {
geyserPingInfo.addPlayer(player.getName());
});
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(),
new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers()),
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;
} catch (Exception e) {
logger.debug("Error while getting Bukkit ping passthrough: " + e.toString());
return new GeyserPingInfo(null, 0, 0);
return new GeyserPingInfo(null, null, null);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

package org.geysermc.platform.sponge;

import com.github.steveice10.mc.protocol.MinecraftConstants;
import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.spongepowered.api.MinecraftVersion;
Expand All @@ -35,6 +36,7 @@
import org.spongepowered.api.event.cause.EventContext;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.StatusClient;
import org.spongepowered.api.profile.GameProfile;

import java.lang.reflect.Method;
import java.net.Inet4Address;
Expand Down Expand Up @@ -68,11 +70,18 @@ public GeyserPingInfo getPingInformation() {
Sponge.getEventManager().post(event);
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
event.getResponse().getDescription().toPlain(),
event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline(),
event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax());
event.getResponse().getPlayers().get().getProfiles().forEach(player -> {
geyserPingInfo.addPlayer(player.getName().orElseThrow(IllegalStateException::new));
});
new GeyserPingInfo.Players(
event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax(),
event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline()
),
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ public GeyserPingInfo getPingInformation() {
throw new RuntimeException(e);
}
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
LegacyComponentSerializer.INSTANCE.serialize(event.getPing().getDescription(), '§'),
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline(),
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax()
LegacyComponentSerializer.legacy().serialize(event.getPing().getDescription(), '§'),
new GeyserPingInfo.Players(
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 -> {
geyserPingInfo.addPlayer(player.getName());
});
event.getPing().getPlayers().get().getSample().stream().map(ServerPing.SamplePlayer::getName).forEach(geyserPingInfo.getPlayerList()::add);
return geyserPingInfo;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,68 @@

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.Getter;

import java.util.ArrayList;
import java.util.Collection;

@Data
public class GeyserPingInfo {

public final String motd;
public final int currentPlayerCount;
public final int maxPlayerCount;
private String description;

@Getter
private Collection<String> players = new ArrayList<>();
private Players players;
private Version version;

public void addPlayer(String username) {
players.add(username);
@JsonIgnore
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
pong.setVersion(null); // Server tries to connect either way and it looks better
pong.setIpv4Port(config.getBedrock().getPort());

if (config.isPassthroughMotd() && pingInfo != null && pingInfo.motd != null) {
String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.motd)).split("\n");
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n");
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.

Expand All @@ -87,8 +87,8 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
}

if (config.isPassthroughPlayerCounts() && pingInfo != null) {
pong.setPlayerCount(pingInfo.currentPlayerCount);
pong.setMaximumPlayerCount(pingInfo.maxPlayerCount);
pong.setPlayerCount(pingInfo.getPlayers().getOnline());
pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax());
} else {
pong.setPlayerCount(connector.getPlayers().size());
pong.setMaximumPlayerCount(config.getMaxPlayers());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,16 @@ private byte[] getGameData() {
}

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.
} else {
motd = connector.getConfig().getBedrock().getMotd1();
}

// If passthrough player counts is enabled lets get players from the server
if (connector.getConfig().isPassthroughPlayerCounts() && pingInfo != null) {
currentPlayerCount = String.valueOf(pingInfo.currentPlayerCount);
maxPlayerCount = String.valueOf(pingInfo.maxPlayerCount);
currentPlayerCount = String.valueOf(pingInfo.getPlayers().getOnline());
maxPlayerCount = String.valueOf(pingInfo.getPlayers().getMax());
} else {
currentPlayerCount = String.valueOf(connector.getPlayers().size());
maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers());
Expand Down Expand Up @@ -220,7 +220,7 @@ private byte[] getPlayers() {

// Fill player names
if(pingInfo != null) {
for (String username : pingInfo.getPlayers()) {
for (String username : pingInfo.getPlayerList()) {
query.write(username.getBytes());
query.write((byte) 0x00);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@

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.MinecraftProtocol;
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 com.nukkitx.nbt.util.VarInts;
import org.geysermc.connector.common.ping.GeyserPingInfo;
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;

public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable {
Expand All @@ -44,13 +49,10 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn

public GeyserLegacyPingPassthrough(GeyserConnector connector) {
this.connector = connector;
this.pingInfo = new GeyserPingInfo(null, 0, 0);
}

private GeyserPingInfo pingInfo;

private Client client;

/**
* Start legacy ping passthrough thread
* @param connector GeyserConnector
Expand All @@ -76,15 +78,51 @@ public GeyserPingInfo getPingInformation() {
@Override
public void run() {
try {
this.client = new Client(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort(), new MinecraftProtocol(SubProtocol.STATUS), new TcpSessionFactory());
this.client.getSession().setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> {
this.pingInfo = new GeyserPingInfo(((TextMessage) info.getDescription()).getText(), info.getPlayerInfo().getOnlinePlayers(), info.getPlayerInfo().getMaxPlayers());
this.client.getSession().disconnect(null);
});

client.getSession().connect();
} catch (Exception ex) {
ex.printStackTrace();
Socket socket = new Socket();
socket.connect(new InetSocketAddress(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort()), 5000);

ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
DataOutputStream handshake = new DataOutputStream(byteArrayStream);
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();
}
}
}

0 comments on commit 8ac5d6e

Please sign in to comment.