Add Query event

This commit is contained in:
Mark Vainomaa
2018-10-23 22:04:15 +03:00
parent 72283af867
commit ea43b8ff60
6 changed files with 524 additions and 28 deletions

View File

@@ -213,6 +213,11 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return query.getQueryMap();
}
@Override
public boolean shouldQueryShowPlugins() {
return query.shouldQueryShowPlugins();
}
public String getMotd() {
return motd;
}
@@ -504,6 +509,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("map")
private String queryMap = "Velocity";
@Comment("Whether plugins should be shown in query response by default or not")
@ConfigKey("show-plugins")
private boolean showPlugins = false;
private Query() {
}
@@ -533,13 +542,18 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return queryMap;
}
public boolean shouldQueryShowPlugins() {
return showPlugins;
}
@Override
public String toString() {
return "Query{"
+ "queryEnabled=" + queryEnabled
+ ", queryPort=" + queryPort
+ ", queryMap=" + queryMap
+ '}';
return "Query{" +
"queryEnabled=" + queryEnabled +
", queryPort=" + queryPort +
", queryMap='" + queryMap + '\'' +
", showPlugins=" + showPlugins +
'}';
}
}
}

View File

@@ -3,7 +3,10 @@ package com.velocitypowered.proxy.protocol.netty;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.velocitypowered.api.event.query.ProxyQueryEvent;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.QueryResponse;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
@@ -17,9 +20,16 @@ import org.apache.logging.log4j.Logger;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.BASIC;
import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.FULL;
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class);
@@ -46,6 +56,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
private volatile List<QueryResponse.PluginInformation> pluginInformationList = null;
private final VelocityServer server;
public GS4QueryHandler(VelocityServer server) {
@@ -81,6 +93,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
queryResponse.writeInt(sessionId);
writeString(queryResponse, Integer.toString(challengeToken));
ctx.writeAndFlush(responsePacket);
break;
}
@@ -97,28 +110,51 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
throw new IllegalStateException("Invalid query packet");
}
// Packet header
queryResponse.writeByte(QUERY_TYPE_STAT);
queryResponse.writeInt(sessionId);
// Build query response
QueryResponse response = QueryResponse.builder()
.hostname(ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()))
.gameVersion(ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING)
.map(server.getConfiguration().getQueryMap())
.currentPlayers(server.getPlayerCount())
.maxPlayers(server.getConfiguration().getShowMaxPlayers())
.proxyPort(server.getConfiguration().getBind().getPort())
.proxyHost(server.getConfiguration().getBind().getHostString())
.players(server.getAllPlayers().stream().map(Player::getUsername).collect(Collectors.toList()))
.proxyVersion("Velocity")
.plugins(server.getConfiguration().shouldQueryShowPlugins() ? getRealPluginInformation() : Collections.emptyList())
.build();
// Start writing the response
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
responseWriter.write("gametype", "SMP");
boolean isBasic = queryMessage.readableBytes() == 0;
responseWriter.write("game_id", "MINECRAFT");
responseWriter.write("version", ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING);
responseWriter.write("plugins", "");
// Call event and write response
server.getEventManager().fire(new ProxyQueryEvent(isBasic ? BASIC : FULL, senderAddress, response)).whenCompleteAsync((event, exc) -> {
// Packet header
queryResponse.writeByte(QUERY_TYPE_STAT);
queryResponse.writeInt(sessionId);
responseWriter.write("map", server.getConfiguration().getQueryMap());
responseWriter.write("numplayers", server.getPlayerCount());
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
// Start writing the response
ResponseWriter responseWriter = new ResponseWriter(queryResponse, isBasic);
responseWriter.write("hostname", event.getResponse().getHostname());
responseWriter.write("gametype", "SMP");
responseWriter.write("game_id", "MINECRAFT");
responseWriter.write("version", event.getResponse().getGameVersion());
responseWriter.writePlugins(event.getResponse().getProxyVersion(), event.getResponse().getPlugins());
responseWriter.write("map", event.getResponse().getMap());
responseWriter.write("numplayers", event.getResponse().getCurrentPlayers());
responseWriter.write("maxplayers", event.getResponse().getMaxPlayers());
responseWriter.write("hostport", event.getResponse().getProxyPort());
responseWriter.write("hostip", event.getResponse().getProxyHost());
if (!responseWriter.isBasic) {
responseWriter.writePlayers(event.getResponse().getPlayers());
}
// Send the response
ctx.writeAndFlush(responsePacket);
}, ctx.channel().eventLoop());
if (!responseWriter.isBasic) {
responseWriter.writePlayers(server.getAllPlayers());
}
break;
}
@@ -126,9 +162,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
throw new IllegalStateException("Invalid query type: " + type);
}
}
// Send the response
ctx.writeAndFlush(responsePacket);
} catch (Exception e) {
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
@@ -142,6 +175,22 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
buf.writeByte(0x00);
}
private List<QueryResponse.PluginInformation> getRealPluginInformation() {
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
List<QueryResponse.PluginInformation> res = pluginInformationList;
if (res == null) {
synchronized (this) {
if (pluginInformationList == null) {
pluginInformationList = res = server.getPluginManager().getPlugins().stream()
.map(PluginContainer::getDescription)
.map(desc -> QueryResponse.PluginInformation.of(desc.getName().orElse(desc.getId()), desc.getVersion().orElse(null)))
.collect(Collectors.toList());
}
}
}
return res;
}
private static class ResponseWriter {
private final ByteBuf buf;
private final boolean isBasic;
@@ -180,7 +229,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
// Ends packet k/v body writing and writes stat player list to
// the packet if this writer is initialized for full stat response
void writePlayers(Collection<Player> players) {
void writePlayers(Collection<String> players) {
if (isBasic) {
return;
}
@@ -189,8 +238,29 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
buf.writeByte(0x00);
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2);
players.forEach(player -> writeString(buf, player.getUsername()));
players.forEach(player -> writeString(buf, player));
buf.writeByte(0x00);
}
void writePlugins(String serverVersion, Collection<QueryResponse.PluginInformation> plugins) {
if (isBasic)
return;
StringBuilder pluginsString = new StringBuilder();
pluginsString.append(serverVersion).append(':').append(' ');
Iterator<QueryResponse.PluginInformation> iterator = plugins.iterator();
while (iterator.hasNext()) {
QueryResponse.PluginInformation info = iterator.next();
pluginsString.append(info.getName());
if (info.getVersion() != null) {
pluginsString.append(' ').append(info.getVersion());
}
if (iterator.hasNext()) {
pluginsString.append(';').append(' ');
}
}
writeString(buf, pluginsString.toString());
}
}
}