package net.glowstone.net.query; import io.netty.channel.ChannelFuture; import net.glowstone.GlowServer; import net.glowstone.net.GlowDatagramServer; import org.bukkit.scheduler.BukkitRunnable; import java.net.InetSocketAddress; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; /** * Implementation of a server for the minecraft server query protocol. * * @see <a href="http://wiki.vg/Query">Protocol Specifications</a> */ public class QueryServer extends GlowDatagramServer { /** * Maps each {@link InetSocketAddress} of a client to its challenge token. */ private Map<InetSocketAddress, Integer> challengeTokens = new ConcurrentHashMap<>(); /** * The task used to invalidate all challenge tokens every 30 seconds. */ private ChallengeTokenFlushTask flushTask; public QueryServer(GlowServer server, CountDownLatch latch, boolean showPlugins) { super(server, latch); bootstrap.handler(new QueryHandler(this, showPlugins)); } /** * Bind the server on the specified address. * * @param address The address. * @return Netty channel future for bind operation. */ public ChannelFuture bind(InetSocketAddress address) { GlowServer.logger.info("Binding query to address " + address + "..."); if (flushTask == null) { flushTask = new ChallengeTokenFlushTask(); flushTask.runTaskTimerAsynchronously(null, 600, 600); } return super.bind(address); } /** * Shut the query server down. */ public void shutdown() { super.shutdown(); if (flushTask != null) { flushTask.cancel(); } } /** * Generate a new token. * * @param address The sender address. * @return The generated valid token. */ public int generateChallengeToken(InetSocketAddress address) { int token = ThreadLocalRandom.current().nextInt(); challengeTokens.put(address, token); return token; } /** * Verify that the request is using the correct challenge token. * * @param address The sender address. * @param token The token. * @return {@code true} if the token is valid. */ public boolean verifyChallengeToken(InetSocketAddress address, int token) { return Objects.equals(challengeTokens.get(address), token); } /** * Invalidates all challenge tokens. */ public void flushChallengeTokens() { challengeTokens.clear(); } @Override public void onBindSuccess(InetSocketAddress address) { GlowServer.logger.info("Successfully bound query to " + address + '.'); super.onBindSuccess(address); } @Override public void onBindFailure(InetSocketAddress address, Throwable t) { GlowServer.logger.warning("Failed to bind query to" + address + '.'); } /** * Inner class for resetting the challenge tokens every 30 seconds. */ private class ChallengeTokenFlushTask extends BukkitRunnable { @Override public void run() { flushChallengeTokens(); } } }